package com.binance.notification.web.push;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;

import com.binance.master.utils.security.EncryptionUtils;
import com.binance.notification.web.utils.Constants;
import com.google.common.collect.Maps;

import cn.jiguang.common.ClientConfig;
import cn.jiguang.common.ServiceHelper;
import cn.jiguang.common.resp.APIConnectionException;
import cn.jiguang.common.resp.APIRequestException;
import cn.jpush.api.device.DeviceClient;
import cn.jpush.api.push.model.Options;
import cn.jpush.api.push.model.PushPayload;
import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class JClient {

    @Value("${jpush.app.key}")
    private String appKey;
    @Value("${jpush.master.secret}")
    private String masterSecret;
    @Value("${jpush.host.name}")
    private String pushHostName; // 私有云服务器接口服务地址
    @Value("${jpush.device.host.name}")
    private String deviceHostName;
    @Value("${jpush.notification.timetolive}")
    private long pushTimeToLive; // 默认离线时长，秒
    @Value("${jpush.notification.ws.address}")
    private String wsAddress;
    @Value("${jpush.web.fixUserStr:true}")
    private boolean fixWebTmpUserStr;

    private String scriptSha;
    private String[] webSocketAddress;
    @Resource
    private RedisTemplate<String, String> redisTemplate;
    @Resource
    private Environment env;
    private boolean apnsProduction;
    private PushClient pushClient;
    private DeviceClient deviceClient;

    private static final ThreadLocal<Context> THREADLOCAL = new ThreadLocal<>();

    @PostConstruct
    public void init() throws Exception {
        this.apnsProduction = "prod".equals(env.getProperty("active"));
        ClientConfig clientConfig = ClientConfig.getInstance();
        clientConfig.setDeviceHostName(deviceHostName);
        clientConfig.setPushHostName(pushHostName);
        clientConfig.setApnsProduction(apnsProduction);
        pushClient = new PushClient(this.masterSecret, this.appKey, null, clientConfig);
        deviceClient = new DeviceClient(masterSecret, appKey, null, clientConfig);
        this.initWebSocketLua();
    }

    public DeviceClient getDeviceClient() {
        return this.deviceClient;
    }

    public String getBasicAuthorization() {
        return ServiceHelper.getBasicAuthorization(appKey, masterSecret);
    }

    public Map<String, String> getWebSDKInitParams(String userStr) {
        if (StringUtils.isBlank(userStr)) {
            if (this.fixWebTmpUserStr) {
                userStr = Constants.GLOBAL_WEB_USER_STR;
            } else {
                userStr = Constants.TMP_WEB_USER_STR_PREFIX + UUID.randomUUID().toString().replaceAll("-", "");
            }
        }
        String random = UUID.randomUUID().toString().replaceAll("-", "");
        Map<String, String> map = Maps.newHashMap();
        map.put("userStr", userStr);
        map.put("wsAddress", this.getWebSocketAddress());
        map.put("appkey", appKey);
        map.put("randomStr", random);
        map.put("timestamp", String.valueOf((System.currentTimeMillis()) / 1000));
        map.put("signature", EncryptionUtils.md5("appkey=" + appKey + "&timestamp=" + map.get("timestamp")
                + "&random_str=" + random + "&user_str=" + userStr + "&key=" + EncryptionUtils.md5(masterSecret)));
        map.put("globalTags", Constants.GLOBAL_TAG);
        map.put("languagePrefix", Constants.LANGUAGE_TAG_PRIX);
        map.put("debug", String.valueOf(!this.apnsProduction));
        return map;
    }

    private void initWebSocketLua() {
        String address = StringUtils.trim(wsAddress);
        if (StringUtils.contains(address, ",")) {
            this.webSocketAddress = StringUtils.split(address, ",");
            if (this.webSocketAddress.length > 1) {
                byte[] chars = new byte[0];
                try (InputStream in = this.getClass().getResourceAsStream("getWebsocketAddress.lua");) {
                    chars = FileCopyUtils.copyToByteArray(in);
                } catch (IOException e) {
                    throw new IllegalArgumentException("getWebsocketAddress.lua文件加载失败");
                }
                String script = new String(chars);
                scriptSha = this.redisTemplate.execute(new RedisCallback<String>() {

                    @Override
                    public String doInRedis(RedisConnection connection) throws DataAccessException {
                        return connection.scriptLoad(script.getBytes());
                    }

                });
            }
        } else {
            this.webSocketAddress = new String[] {this.wsAddress};
        }
    }

    private String getWebSocketAddress() {
        if (this.webSocketAddress.length > 1) {
            return this.redisTemplate.execute(new RedisCallback<String>() {

                @Override
                public String doInRedis(RedisConnection connection) throws DataAccessException {
                    Object result = connection.evalSha(scriptSha, ReturnType.VALUE, 1,
                            "jpush.websocket.address".getBytes(), wsAddress.getBytes());
                    return new String((byte[]) result);
                }
            });

        }

        return this.webSocketAddress[0];
    }

    /**
     * 同步推送通知
     * 
     * @param payloadString
     * @return json格式字符串的通知体
     * @throws APIConnectionException
     * @throws APIRequestException
     */
    public PushResult sendPushPayloadString(List<String> payloadString) {
        if (log.isInfoEnabled()) {
            log.info("共{}条：{}", payloadString.size(), payloadString);
        }
        return this.pushClient.sendPushPayloadString(payloadString);
    }

    /**
     * 同步推送通知
     * 
     * @param payloadString
     * @return json格式字符串的通知体
     * @throws APIConnectionException
     * @throws APIRequestException
     */
    public PushResult sendPushPayloadString(String... payloadString) {
        return this.sendPushPayloadString(Arrays.asList(payloadString));
    }

    public PushResult sendPushPayload(List<PushPayload> pushPayload) {
        if (log.isInfoEnabled()) {
            log.info("共{}条：{}", pushPayload.size(), pushPayload);
        }
        PushResult result = this.pushClient.sendPushPayload(pushPayload);
        return result;
    }

    public PushResult sendPushPayload(PushPayload... pushPayload) {
        PushResult result = this.sendPushPayload(Arrays.asList(pushPayload));
        return result;
    }

    public Options.Builder newOptionsBuilder(long timeToLive) {
        Options.Builder optionsBuilder = Options.newBuilder();
        if (timeToLive >= 0) {
            optionsBuilder.setTimeToLive(timeToLive);
        } else {
            optionsBuilder.setTimeToLive(pushTimeToLive);
        }
        optionsBuilder.setApnsProduction(apnsProduction);
        return optionsBuilder;
    }

    /**
     * 设置当前线程推送时不需要返回推送结果
     */
    public static void setNoNeedPushResult() {
        JClient.getContext().put(Constants.NEED_PUSH_RESULT, false);
    }

    /**
     * 当前线程推送时是否需要返回推送结果
     * 
     * @return
     */
    public static boolean needPushResult() {
        return !Objects.equals(JClient.getContext().get(Constants.NEED_PUSH_RESULT), false);
    }

    /**
     * 返回当前线程推送的上下文对象
     * 
     * @return
     */
    public static Context getContext() {
        Context context = THREADLOCAL.get();
        if (context == null) {
            context = new Context();
            THREADLOCAL.set(context);
        }
        return context;
    }

    public static void removeContext() {
        THREADLOCAL.remove();
    }

    /**
     * 推送的上下文对象，JClient内部将其绑定到了当前处理线程
     *
     * @author wangxiao
     * @date 2018-08-07 14:53:16
     */
    public static class Context {
        private Map<String, Object> map;

        private Context() {}

        public Context put(String key, Object value) {
            if (this.map == null) {
                map = Maps.newHashMap();
            }
            this.map.put(key, value);
            return this;
        }

        public Object get(String key) {
            if (this.map == null) {
                return null;
            }
            return this.map.get(key);
        }
    }


}
